#include "../config.h"

#include <glibmm/i18n.h>
#include <boost/format.hpp>

#include "application.h"
#include "command.h"
#include "mainwindow.h"
#include "processview.h"
#include "peercast.h"
#include "log.h"
#include "paths.h"
#include "pref/p_dialog.h"

namespace gpyp {

//========================
// MenuBar
void MenuBar::add_menu(const Glib::ustring &label, Gtk::Menu &menu) {
  Gtk::MenuItem *mi = Gtk::manage(new Gtk::MenuItem(label, true));
  mi->set_submenu(menu);
  append(*mi);
}

MenuBar::MenuBar(Application &app,
                 MainWindow  &mainwindow,
                 ChannelList &channellist) :
  yp_      (app),
  ch_      (app),
  tool_    (app),
  view_    (mainwindow, channellist),
  settings_(app, mainwindow),
  help_    (mainwindow)
{
  add_menu(_("_YellowPage"), yp_);
  add_menu(_("_Channel"),    ch_);
  add_menu(_("_Tool"),       tool_);
  add_menu(_("_View"),       view_);
  add_menu(_("_Settings"),   settings_);
  add_menu(_("_Help"),       help_);
}

//========================
// MainWindow
void MainWindow::save_position() {
  int x, y;
  get_position(x, y);
  left_ = x;
  top_  = y;
}
void MainWindow::restore_position() {
  move(*left_, *top_);
}

void MainWindow::on_presave() {
  conf::Element::on_presave();
  int width, height;
  get_size(width, height);
  width_  = width;
  height_ = height;
}
void MainWindow::conf_on_loaded() {
  panel_position_on_changed();
  panel_position_->signal_changed().connect(sigc::mem_fun(*this, &MainWindow::panel_position_on_changed));
}

void MainWindow::on_realize() {
  show_menubar_  ->attach(menubar_.property_visible());
  show_filterbar_->attach(filterbar_->filterbar().property_visible());
  show_statusbar_->attach(statusbar_->property_visible());
  set_default_size(*width_, *height_);
  resize(*width_, *height_);
  Gtk::Window::on_realize();
  resize(*width_, *height_);

  Glib::signal_idle()
    .connect(sigc::mem_fun(*this, &MainWindow::realize_idle));
}
void MainWindow::on_map() {
  Gtk::Window::on_map();
  restore_position();
  channellist_->grab_focus();
}
void MainWindow::on_unmap() {
  save_position();
  Gtk::Window::on_unmap();
}
void MainWindow::on_hide() {
  save_position();
  get_window()->hide();
}
bool MainWindow::on_delete_event(GdkEventAny *event) {
  bool res = Window::on_delete_event(event);
#ifndef DISABLE_STATUS_ICON
  if (*app_.conf().general()->avoid_close() && *status_icon_->enable()) {
    shown_ = false;
  } else {
    Gtk::Main::quit();
  }
#else
  Gtk::Main::quit();
#endif
  return res;
}

void MainWindow::prepare_commandopts(CommandOpts &opts) {
  opts.search = &filterbar_->searchbar();
}

void MainWindow::resizable_on_changed() {
  if (*resizable_) {
    set_size_request(-1, -1);
    set_resizable(true);
  } else {
    int width, height;
    get_size(width, height);
    set_size_request(width, height);
    set_resizable(false);
  }
}

void MainWindow::panel_position_on_changed() {
  if (paned_) {
    paned_->remove(sw_channellist_);
    paned_->remove(*panel_);
    vbox_.remove(*paned_);
    delete paned_;
    paned_ = NULL;
  } else if (sw_channellist_.get_parent()) {
    vbox_.remove(sw_channellist_);
  }
  bool bef = false;
  switch(*panel_position_) {
  case PANEL_POSITION_LEFT:
    bef = true;
  case PANEL_POSITION_RIGHT:
    paned_ = new Gtk::HPaned();
    break;
  case PANEL_POSITION_TOP:
    bef = true;
  case PANEL_POSITION_BOTTOM:
    paned_ = new Gtk::VPaned();
    break;
  case PANEL_POSITION_NONE:
  default:
    break;
  }
  if (paned_) {
    if (panel_ == NULL) panel_ = new Panel(app_, conf_panel_, panel_position_, *paned_);
    if (bef) {
      paned_->pack1(*panel_,         false, true);
      paned_->pack2(sw_channellist_, true,  false);
    } else {
      paned_->pack1(sw_channellist_, true,  false);
      paned_->pack2(*panel_,         false, true);
    }
    paned_->show_all();
    vbox_.pack_start(*paned_, true, true);
  } else {
    if (panel_) {
      delete panel_;
      panel_ = NULL;
    }
    vbox_.pack_start(sw_channellist_, true, true);
  }
}

bool MainWindow::init_idle() {
#ifndef DISABLE_STATUS_ICON
  if (*shown_ || !*status_icon_->enable()) {
    show();
  }
#else
  show();
#endif
  return false;
}
bool MainWindow::realize_idle() {
  resizable_on_changed();
  resizable_->signal_changed().connect(sigc::mem_fun(*this, &MainWindow::resizable_on_changed));
  return false;
}

#ifndef DISABLE_STATUS_ICON
void MainWindow::status_icon_on_activate() {
  Glib::RefPtr<Gdk::Window> w = get_window();
  if (is_realized() && w->is_visible()) {
    hide();
  } else {
    show();
  }
}
void MainWindow::status_icon_enable_on_changed() {
  if (!*status_icon_->enable()) show();
}
#endif

bool MainWindow::update_loading_count_timeout() {
  size_t count = app_.ypcollection()->get_loading_count();
  if (count) {
    Glib::ustring text = format(_("%s - Reloading...%i"),
                                PACKAGE_STRING, count);
    set_title(text);
#ifndef DISABLE_STATUS_ICON
    status_icon_->set_icon(Gtk::Stock::REFRESH);
    status_icon_->set_tooltip(text);
#endif
    loading_count_shown_ = true;
  } else if (loading_count_shown_) {
    set_title(PACKAGE_STRING);
#ifndef DISABLE_STATUS_ICON
    status_icon_->reset_icon();
    status_icon_->set_tooltip(PACKAGE_STRING);
#endif
    loading_count_shown_ = false;
  }
  conn_update_loading_count_.disconnect();
  return false;
}
void MainWindow::ypcollection_on_loading_count_changed(size_t /*count*/) {
  if (loading_count_shown_) {
    update_loading_count_timeout();
  } else if (!conn_update_loading_count_) {
    conn_update_loading_count_ = Glib::signal_timeout()
      .connect(sigc::mem_fun(*this, &MainWindow::update_loading_count_timeout), 500);
  }
}

void MainWindow::show() {
  if (is_realized()) {
    get_window()->show();
  } else {
    Gtk::Window::show();
  }
  restore_position();
  shown_ = true;
}
void MainWindow::hide() {
  if (is_realized()) {
    save_position();
    get_window()->hide();
  }
  shown_ = false;
}

pref::Dialog &MainWindow::pref() {
  if (!pref_dialog_) pref_dialog_.reset(new pref::Dialog(app_, *this));
  return *pref_dialog_;
}

void MainWindow::show_pref() {
  pref().show();
}
void MainWindow::show_config_view() {
  if (!config_view_dialog_) {
    config_view_dialog_.reset(new ConfigViewDialog(app_, *this));
  }
  config_view_dialog_->show();
}
void MainWindow::show_help_bc() {
  if (!help_bc_dialog_) {
    help_bc_dialog_.reset(new HelpBCDialog(app_, *this));
  }
  help_bc_dialog_->show();
}
void MainWindow::show_help_opts() {
  if (!help_opts_dialog_) {
    help_opts_dialog_.reset(new HelpOptionDialog(app_, *this));
  }
  help_opts_dialog_->show();
}
void MainWindow::show_env_info() {
  if (!env_info_dialog_) {
    env_info_dialog_.reset(new EnvInfoDialog(app_, *this));
  }
  env_info_dialog_->show();
}
void MainWindow::show_about() {
  if (!about_dialog_) {
    about_dialog_.reset(new AboutDialog(app_, *this));
  }
  about_dialog_->show();
}

void MainWindow::uptest(RefYellowPage yp) {
  if (!uptest_dialog_) uptest_dialog_.reset(new UptestDialog(app_, *this));
  uptest_dialog_->set(yp);
  uptest_dialog_->show();
}
void MainWindow::ypinfo(RefYellowPage yp) {
  if (!yp) return;
  if (!ypinfo_dialog_) ypinfo_dialog_.reset(new YPInfoDialog(app_, *this));
  ypinfo_dialog_->set(yp);
  ypinfo_dialog_->show();
}
void MainWindow::chinfo(RefChannel ch) {
  if (!chinfo_dialog_) chinfo_dialog_.reset(new ChInfoDialog(app_, *this));
  if (!ch) ch = channellist_->get_selected_channel();
  chinfo_dialog_->set(ch);
  chinfo_dialog_->show();
}

bool MainWindow::bc_pref_show(ShellLine &line, CommandOpts &/*opts*/) {
  if (line.args.size()) {
    pref().show(line.args.front());
  } else {
    show_pref();
  }
  return true;
}

bool MainWindow::bc_uptest(ShellLine &/*line*/, CommandOpts &opts) {
  RefYellowPage yp;
  if (opts.yp)        yp = opts.yp;
  if (!yp && opts.ch) yp = app_.ypcollection()->find(opts.ch->get_yp());
  uptest(yp);
  return true;
}

bool MainWindow::bc_yp_viewinfo(ShellLine &/*line*/, CommandOpts &opts) {
  if (opts.yp) {
    ypinfo(opts.yp);
    return true;
  }
  return false;
}
bool MainWindow::bc_ch_rematch_favorite(ShellLine &/*line*/, CommandOpts &opts) {
  if (opts.ch) {
    opts.ch->rematch_favorite();
    channellist_->queue_draw();
    return true;
  }
  return false;
}
bool MainWindow::bc_ch_viewinfo(ShellLine &/*line*/, CommandOpts &opts) {
  if (opts.ch) {
    chinfo(opts.ch);
    return true;
  }
  return false;
}

MainWindow::MainWindow(Application &app) :
  conf::Element           ("mainwindow"),
  WidgetInput<Gtk::Window>(app, app.conf().input()->mainwindow()),
  app_                    (app),
  left_                   ("left",              0),
  top_                    ("top",               0),
  width_                  ("width",           640),
  height_                 ("height",          480),
  shown_                  ("shown",          true),
  resizable_              ("resizable",      true),

  show_menubar_           ("show_menubar",   true),
  show_filterbar_         ("show_filterbar", true),
  show_statusbar_         ("show_statusbar", true),
  panel_position_         ("panel_position", PANEL_POSITION_BOTTOM),
  conf_panel_             (new PanelElement("panel")),

  paned_                  (NULL),
  channellist_            (new ChannelList(app_, *this)),
  panel_                  (NULL),

  menubar_                (app_, *this, *channellist_),
  filterbar_              (new FilterBar(app_, *this)),
  statusbar_              (new StatusBar(app_)),
#ifndef DISABLE_STATUS_ICON
  status_icon_            (new StatusIcon(app_, IconManager::GPYP)),
  notify_                 (new Notify(app_, status_icon_)),
#else
  notify_                 (new Notify(app_)),
#endif
  loading_count_shown_    (false),
  bc_pref_                (*app_.bc().add_ns("Pref"))
{
  conf::Element::add(left_);
  conf::Element::add(top_);
  conf::Element::add(width_);
  conf::Element::add(height_);
  conf::Element::add(shown_);
  conf::Element::add(resizable_);

  conf::Element::add(show_menubar_);
  conf::Element::add(show_filterbar_);
  conf::Element::add(show_statusbar_);
  conf::Element::add(panel_position_);
  conf::Element::add(conf_panel_);

  conf::Element::add(filterbar_);
  conf::Element::add(channellist_);
  conf::Element::add(statusbar_);

#ifndef DISABLE_STATUS_ICON
  conf::Element::add(status_icon_);
#endif
  conf::Element::add(notify_);

#define ADD_CMD_F(BC, CMD, FUNC, H)                                     \
  (BC).add_cmd(#CMD, sigc::mem_fun(*this, &MainWindow::bc_##FUNC), H)
#define ADD_CMD_G(CMD, H) ADD_CMD_F(app_.bc(), CMD, CMD, H)
#define ADD_CMD_B(BC, NS, CMD, H) ADD_CMD_F(BC, CMD, NS##_##CMD, H)
#define ADD_CMD(NS, CMD, H) ADD_CMD_F(bc_##NS##_, CMD, NS##_##CMD, H)
  ADD_CMD(pref, show, _("Show preferences dialog"));

  ADD_CMD_G(uptest, _("Show uptest dialog"));

  RefYPCollection ypc = app_.ypcollection();
  ADD_CMD_B(ypc->get_bc_yp(), yp, viewinfo, _("Show YP information dialog"));
  ADD_CMD_B(ypc->get_bc_ch(), ch, rematch_favorite, _("Rematch favorites"));
  ADD_CMD_B(ypc->get_bc_ch(), ch, viewinfo, _("Show Channel information dialog"));
#undef ADD_CMD
#undef ADD_CMD_B
#undef ADD_CMD_G
#undef ADD_CMD_F

#ifndef DISABLE_STATUS_ICON
  status_icon_->set_tooltip(PACKAGE_STRING);
  status_icon_->signal_activate().connect(sigc::mem_fun(*this, &MainWindow::status_icon_on_activate));
  status_icon_->enable()->signal_changed()
    .connect(sigc::mem_fun(*this, &MainWindow::status_icon_enable_on_changed));
#endif

  app_.ypcollection()->signal_loading_count_changed()
    .connect(sigc::mem_fun(*this, &MainWindow::ypcollection_on_loading_count_changed));

  sw_channellist_.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
  sw_channellist_.add(*channellist_);
  sw_channellist_.show_all();

  vbox_.pack_start(menubar_, false, true);
  vbox_.pack_start(*filterbar_, false, true);
  /*
  vpaned_.pack1   (sw_channellist_, true, false);
  vpaned_.pack2   (*panel_, false, true);
  vbox_.pack_start(vpaned_, true, true);
  */
  vbox_.pack_end(*statusbar_, false, true);
  Gtk::Window::add(vbox_);

  vbox_.show_all();
  set_title(PACKAGE_STRING);

  Glib::signal_idle().connect(sigc::mem_fun(*this, &MainWindow::init_idle));
  app_.conf().signal_loaded().connect(sigc::mem_fun(*this, &MainWindow::conf_on_loaded));
}
MainWindow::~MainWindow() {
  if (paned_) delete paned_;
  if (panel_) delete panel_;
}

//========================
// FilterBar::ModelSelector
void FilterBar::ModelSelector::append(const Glib::ustring &name,
                                      CLModel              model) {
  Gtk::TreeRow row   = *liststore_->append();
  row[column_name_]  = name;
  row[column_model_] = model;
}

void FilterBar::ModelSelector::model_on_changed() {
  CLModel model = *app_.mainwindow()->channellist()->model();
  Gtk::TreeNodeChildren children = liststore_->children();
  for(Gtk::TreeIter iter = children.begin();
      iter != children.end(); ++iter) {
    if (iter->get_value(column_model_) == model) {
      set_active(iter);
      break;
    }
  }
  app_.mainwindow()->channellist()->grab_focus();
}
void FilterBar::ModelSelector::main_on_run() {
  model_on_changed();
  app_.mainwindow()->channellist()->model()->signal_changed()
    .connect(sigc::mem_fun(*this, &ModelSelector::model_on_changed));
}

void FilterBar::ModelSelector::on_changed() {
  app_.mainwindow()->channellist()->model() =
    get_active()->get_value(column_model_);
  Gtk::ComboBox::on_changed();
}

FilterBar::ModelSelector::ModelSelector(Application &app) :
  app_(app)
{
  columns_.add(column_name_);
  columns_.add(column_model_);
  liststore_ = Gtk::ListStore::create(columns_);
  append(_("Default"), CLMODEL_DEFAULT);
  append(_("Played"),  CLMODEL_PLAYED);
  append(_("Ended"),   CLMODEL_ENDED);
  set_model(liststore_);
  pack_start(column_name_);

  Gtk::Main::signal_run().connect(sigc::mem_fun(*this, &ModelSelector::main_on_run));
}

//========================
// FilterBar::Filter
void FilterBar::Filter::update_visible() {
  bool refilter = get_active();
  property_visible() = get_enable() && visible_;
  if (refilter) {
    RefMainWindow mw = app_.mainwindow();
    if (mw) mw->channellist()->refilter();
  }
}

bool FilterBar::Filter::on_button_press_event(GdkEventButton* event) {
  if (event->type == GDK_BUTTON_PRESS) {
    signal_clicked_(event);
    return true;
  } else if (event->type == GDK_2BUTTON_PRESS && event->button == 1) {
    start_rename();
    return true;
  }
  return Gtk::ToggleButton::on_button_press_event(event);
}

bool FilterBar::Filter::entry_on_key_press_event(GdkEventKey *event) {
  if (event->type == GDK_KEY_PRESS && event->keyval == GDK_Escape) {
    label_.show();
    entry_.hide();
    return true;
  }
  return false;
}
void FilterBar::Filter::entry_on_activate() {
  Glib::ustring name = entry_.get_text();
  if (!name.empty()) favorite_->name() = name;
  label_.show();
  entry_.hide();
}
#ifdef GTK_ENTRY_HAS_ICON
void FilterBar::Filter::entry_on_icon_press(Gtk::EntryIconPosition pos,
                                            const GdkEventButton  *event) {
  if (event->button == 1) {
    if (event->type == GDK_BUTTON_PRESS &&
        pos == Gtk::ENTRY_ICON_SECONDARY) {
      label_.show();
      entry_.hide();
    }
  }
}
#endif

bool FilterBar::Filter::get_enable() {
  return *favorite_->enable() && *favorite_->filter();
}
void FilterBar::Filter::set_visible(bool visible) {
  visible_ = visible;
  update_visible();
}

void FilterBar::Filter::start_rename() {
  entry_.set_text(*favorite_->name());
  label_.hide();
  entry_.show();
  entry_.grab_focus();
}

FilterBar::Filter::Filter(Application &app, RefFavorite favorite) :
  app_     (app),
  favorite_(favorite),
  visible_ (true)
{
  favorite_->name()->attach(label_.property_label());

  label_.set_alignment(Gtk::ALIGN_LEFT);
  label_.show();

  entry_.set_width_chars(10);
  entry_.modify_text(Gtk::STATE_ACTIVE,   Gdk::Color("black"));
  entry_.modify_text(Gtk::STATE_PRELIGHT, Gdk::Color("black"));
  entry_.modify_base(Gtk::STATE_ACTIVE,   Gdk::Color("white"));
  entry_.modify_base(Gtk::STATE_PRELIGHT, Gdk::Color("white"));
  entry_.signal_key_press_event().connect(sigc::mem_fun(*this, &Filter::entry_on_key_press_event));
  entry_.signal_activate().connect(sigc::mem_fun(*this, &Filter::entry_on_activate));
#ifdef GTK_ENTRY_HAS_ICON
  entry_.set_icon_from_stock(Gtk::Stock::CANCEL, Gtk::ENTRY_ICON_SECONDARY);
  entry_.set_icon_activatable(true, Gtk::ENTRY_ICON_SECONDARY);
  entry_.signal_icon_press().connect(sigc::mem_fun(*this, &Filter::entry_on_icon_press));
#endif

  box_.pack_start(label_);
  box_.pack_start(entry_);
  box_.show();

  add(box_);

  favorite_->enable()->signal_changed().connect(sigc::mem_fun(*this, &FilterBar::Filter::update_visible));
  favorite_->filter()->signal_changed().connect(sigc::mem_fun(*this, &FilterBar::Filter::update_visible));

  set_no_show_all(!get_enable());
  update_visible();
  set_relief(Gtk::RELIEF_NONE);
  add_events(Gdk::BUTTON_PRESS_MASK);
}

//========================
// FilterBar::FilterMenu
void FilterBar::FilterMenu::refresh_color() {
  app_.ypcollection()->rematch_favorite();
  app_.mainwindow()->channellist()->queue_draw();
}
void FilterBar::FilterMenu::refresh_sort() {
  app_.ypcollection()->rematch_favorite();
  app_.mainwindow()->channellist()->resort();
}

void FilterBar::FilterMenu::filter_on_toggled() {
  filter_->get_favorite()->filter() = mi_filter_.get_active();
}
void FilterBar::FilterMenu::bgcolor_on_selected(const Color &color) {
  filter_->get_favorite()->bgcolor() = color;
  refresh_color();
}
void FilterBar::FilterMenu::fgcolor_on_selected(const Color &color) {
  filter_->get_favorite()->fgcolor() = color;
  refresh_color();
}
void FilterBar::FilterMenu::sort_on_changed(ChSort sort) {
  filter_->get_favorite()->sort() = sort;
  refresh_sort();
}
void FilterBar::FilterMenu::remove_on_activate() {
  app_.favcollection()->remove_item(filter_->get_favorite());
}
void FilterBar::FilterMenu::rename_on_activate() {
  filter_->start_rename();
}
void FilterBar::FilterMenu::setting_on_activate() {
  static DialogWrapper<pref::FavoriteDialog> d;
  if (!d) d.reset(new pref::FavoriteDialog(app_, parent_));
  d->show(filter_->get_favorite());
}

void FilterBar::FilterMenu::set(Filter &filter) {
  filter_ = &filter;

  RefFavorite favorite = filter_->get_favorite();
  mi_filter_.set_active(*favorite->filter());

  ColorSelectMenu *bg = new ColorSelectMenu(app_, *favorite->bgcolor());
  ColorSelectMenu *fg = new ColorSelectMenu(app_, *favorite->fgcolor());
  bg->signal_selected()
    .connect(sigc::mem_fun(*this, &FilterMenu::bgcolor_on_selected));
  fg->signal_selected()
    .connect(sigc::mem_fun(*this, &FilterMenu::fgcolor_on_selected));
  mi_bgcolor_.set_submenu(*Gtk::manage(bg));
  mi_fgcolor_.set_submenu(*Gtk::manage(fg));

  menu_sort_.set(*filter_->get_favorite()->sort());
}

FilterBar::FilterMenu::FilterMenu(Application &app, Gtk::Window &parent) :
  app_       (app),
  parent_    (parent),
  filter_    (NULL),
  mi_filter_ (_("_Filter"),     true),
  mi_bgcolor_(_("_Background"), true),
  mi_fgcolor_(_("_Text"),       true),
  mi_sort_   (_("_Sort"),       true),
  mi_rename_ (_("Re_name"),  sigc::mem_fun(*this, &FilterMenu::rename_on_activate)),
  mi_setting_(_("_Setting"), sigc::mem_fun(*this, &FilterMenu::setting_on_activate), Gtk::Stock::PREFERENCES),
  mi_remove_ (_("_Remove"),  sigc::mem_fun(*this, &FilterMenu::remove_on_activate),  Gtk::Stock::REMOVE)
{
  mi_filter_.signal_toggled().connect(sigc::mem_fun(*this, &FilterMenu::filter_on_toggled));

  append(mi_filter_);

  append(mi_bgcolor_);
  append(mi_fgcolor_);

  menu_sort_.signal_changed().connect(sigc::mem_fun(*this, &FilterMenu::sort_on_changed));
  mi_sort_.set_submenu(menu_sort_);
  append(mi_sort_);

  append(mi_rename_);
  append(mi_setting_);
  append(mi_remove_);
  show_all();
}

//========================
// FilterBar::Selector::Scroller
void FilterBar::Selector::Scroller::left_on_clicked() {
  selector_.scroll_left();
}
void FilterBar::Selector::Scroller::right_on_clicked() {
  selector_.scroll_right();
}

FilterBar::Selector::Scroller::Scroller(Selector &selector) :
  selector_(selector),
  a_left_  (Gtk::ARROW_LEFT,  Gtk::SHADOW_IN),
  a_right_ (Gtk::ARROW_RIGHT, Gtk::SHADOW_IN)
{
  b_left_.add(a_left_);
  b_left_.set_relief(Gtk::RELIEF_NONE);
  b_left_.signal_clicked()
    .connect(sigc::mem_fun(*this, &Scroller::left_on_clicked));

  b_right_.add(a_right_);
  b_right_.set_relief(Gtk::RELIEF_NONE);
  b_right_.signal_clicked()
    .connect(sigc::mem_fun(*this, &Scroller::right_on_clicked));

  pack_start(b_left_);
  pack_start(b_right_);
  show_all();
}

//========================
// FilterBar::Selector
void FilterBar::Selector::on_size_request(Gtk::Requisition *requisition) {
  Gtk::HBox::on_size_request(requisition);
  requisition->width = (left_box_.get_allocation().get_width() +
                        right_box_.get_allocation().get_width());
}
void FilterBar::Selector::on_size_allocate(Gtk::Allocation &allocation) {
  Gtk::HBox::on_size_allocate(allocation);
  update_size(allocation);
}

void FilterBar::Selector::update_size(const Gtk::Allocation &allocation,
                                      bool                   force) {
  if (force || allocation.get_width() != last_width_) {
    last_width_ = allocation.get_width();

    int  spacing     = get_spacing();
    long max_width   = (allocation.get_width() - (get_border_width() * 2) -
                        right_box_.get_allocation().get_width() - spacing);
    long max_width_a = max_width;
    long width       = left_box_.get_allocation().get_width() + spacing;

    {
      Gtk::Requisition req;
      scroller_.size_request(req);
      max_width_a -= req.width;
    }

    size_t i    = 0;
    bool   over = false;
    FilterList::iterator iter_h, iter_h_a = filters_.end();
    for(FilterList::iterator iter = filters_.begin();
        iter != filters_.end(); ++iter) {
      bool e = iter->get_enable();
      if (i < show_start_) {
        iter->set_visible(false);
      } else {
        if (e) {
          Gtk::Requisition req;
          iter->size_request(req);
          width += req.width;
        }
        if (width > max_width_a && iter_h_a == filters_.end()) {
          iter_h_a = iter;
        }
        if (width > max_width) {
          iter_h = iter;
          over = true;
          break;
        } else {
          iter->set_visible(true);
        }
        if (e) width += spacing;
      }
      if (e) ++i;
    }

    bool right = (show_start_ > 0 && iter_h_a != filters_.end()) || over;
    scroller_.set_left_scrollable(show_start_ > 0);
    scroller_.set_right_scrollable(right);
    if (show_start_ > 0 || over) {
      for(; iter_h_a != filters_.end(); ++iter_h_a) {
        iter_h_a->set_visible(false);
      }
      scroller_.show();
    } else {
      scroller_.hide();
    }
  }
}

void FilterBar::Selector::refilter() {
  if (!stop_refilter_) {
    RefMainWindow mw = app_.mainwindow();
    if (mw) mw->channellist()->refilter();
  }
}

FilterBar::Filter *FilterBar::Selector::add(RefFavorite favorite) {
  Filter *filter = new Filter(app_, favorite);
  filter->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &Selector::filter_on_clicked), filter));
  filter->signal_toggled().connect(sigc::mem_fun(*this, &Selector::refilter));
  filters_.push_back(filter);
  pack_start(*filter, false, false);
  return filter;
}

void FilterBar::Selector::on_add_item(RefFavorite favorite) {
  add(favorite);
}
void FilterBar::Selector::on_remove_item(RefFavorite favorite) {
  for(FilterList::iterator iter = filters_.begin(); iter != filters_.end(); ++iter) {
    if (iter->get_favorite() == favorite) {
      remove(*iter);
      filters_.erase(iter);
      refilter();
      return;
    }
  }
  log::err << "bug: FilterBar::Selector::on_remove_item" << log::endl;
}
void FilterBar::Selector::on_reorder_item(RefFavorite favorite,
                                          RefFavorite prev,
                                          RefFavorite next) {
  int tpos = -1, ppos = -1, npos = -1;
  struct Data {
    Filter *filter;
    gint    pos;
    Data   *next;
  } *d_f = NULL, *d_c = NULL;
  Glib::ListHandle<Gtk::Widget *> children = get_children();
  for(Glib::ListHandle<Gtk::Widget *>::iterator iter = children.begin(); iter != children.end(); ++iter) {
    Filter *filter = dynamic_cast<Filter *>(*iter);
    if (filter) {
      gint pos = -1;
      gtk_container_child_get(GTK_CONTAINER(gobj()), (*iter)->gobj(),
                              "position", &pos,
                              NULL);
      Data *d   = new Data();
      d->filter = filter;
      d->pos    = pos;
      d->next   = NULL;
      if (!d_c) {
        d_f = d_c = d;
      } else {
        d_c->next = d;
        d_c = d;
      }
      RefFavorite f = filter->get_favorite();
      if      (f == favorite) tpos = pos;
      else if (f == prev)     ppos = pos;
      else if (f == next)     npos = pos;
    }
  }

  if (tpos >= 0 && (ppos >= 0 || npos >= 0)) {
    log::dbg << boost::format("%02i:%02i:%02i") % tpos % ppos % npos << " >> ";
    for(Data *d = d_f; d; d = d->next) {
      int pos = d->pos;
      if (pos == tpos) {
        size_t p = 0;
        if (ppos >= 0) p = tpos > ppos ? ppos + 1 : ppos;
        else           p = tpos < npos ? npos - 1 : npos;
        gtk_box_reorder_child(GTK_BOX(gobj()), GTK_WIDGET(d->filter->gobj()), pos = p);
        log::dbg << "*";
      } else if (npos >= 0 && pos >= npos && pos < tpos) {
        gtk_box_reorder_child(GTK_BOX(gobj()), GTK_WIDGET(d->filter->gobj()), ++pos);
        log::dbg << "+";
      } else if (ppos >= 0 && pos > tpos && pos <= ppos) {
        gtk_box_reorder_child(GTK_BOX(gobj()), GTK_WIDGET(d->filter->gobj()), --pos);
        log::dbg << "-";
      } else {
        log::dbg << " ";
      }
      log::dbg << boost::format("%02i") % pos << " ";
    }
    log::dbg << "<<" << log::endl;
  } else {
    log::dbg << "bug: FilterBar::Selector::favorites_on_item_reordered" << log::endl;
  }
  for(d_c = d_f; d_c;) {
    d_f = d_c;
    d_c = d_c->next;
    delete d_f;
  }
}
void FilterBar::Selector::on_clear() {
  for(FilterList::iterator iter = filters_.begin();
      iter != filters_.end(); ++iter) {
    remove(*iter);
  }
  filters_.clear();
}

void FilterBar::Selector::filter_on_clicked(GdkEventButton *event, Filter *filter) {
  stop_refilter_ = true;
  switch(event->button) {
  case 1:
    for(FilterList::iterator iter = filters_.begin(); iter != filters_.end(); ++iter) {
      iter->set_active(&*iter == filter);
    }
    break;
  case 2:
    filter->set_active(!filter->get_active());
    break;
  case 3:
    filter_menu_.set(*filter);
    filter_menu_.popup(event->button, event->time);
    break;
  default:
    stop_refilter_ = false;
    return;
  }
  stop_refilter_ = false;
  refilter();
}
bool FilterBar::Selector::on_scroll_event(GdkEventScroll *event) {
  if (filters_.size() > 1) {
    gint offset = 0;
    if (event->direction == GDK_SCROLL_UP ||
        event->direction == GDK_SCROLL_LEFT) {
      offset = -1;
    } else if (event->direction == GDK_SCROLL_DOWN ||
               event->direction == GDK_SCROLL_RIGHT) {
      offset = 1;
    } else {
      return false;
    }

    GList *children = gtk_container_get_children(GTK_CONTAINER(gobj()));
    GtkToggleButton *sel = NULL, *next = NULL, *min = NULL, *max = NULL;
    gint sel_pos = -1, next_pos = -1, min_pos = -1, max_pos = -1;
    for(GList *c = children; c; c = c->next) {
      if (c->data &&
          GTK_IS_TOGGLE_BUTTON(c->data) &&
          GTK_WIDGET_VISIBLE(c->data)) {
        GtkToggleButton *tb = GTK_TOGGLE_BUTTON(c->data);
        gint pos = -1;
        gtk_container_child_get(GTK_CONTAINER(gobj()), GTK_WIDGET(c->data),
                                "position", &pos,
                                NULL);
        if (min_pos < 0 || pos < min_pos) {
          min = tb;
          min_pos = pos;
        }
        if (max_pos < 0 || pos > max_pos) {
          max = tb;
          max_pos = pos;
        }
        if (gtk_toggle_button_get_active(tb)) {
          if (sel) {
            g_list_free(children);
            return false;
          } else {
            sel = tb;
            sel_pos = pos;
          }
        } else if (sel && !next && pos == sel_pos + offset) {
          next = GTK_TOGGLE_BUTTON(c->data);
          break;
        }
      }
    }
    if (sel && !next) {
      for(GList *c = children; c; c = c->next) {
        if (c->data &&
            GTK_IS_TOGGLE_BUTTON(c->data) &&
            GTK_WIDGET_VISIBLE(c->data)) {
          GtkToggleButton *tb = GTK_TOGGLE_BUTTON(c->data);
          if (!gtk_toggle_button_get_active(tb)) {
            gint pos = -1;
            gtk_container_child_get(GTK_CONTAINER(gobj()), GTK_WIDGET(c->data),
                                    "position", &pos,
                                    NULL);
            if (offset < 0) {
              if (pos >= min_pos && pos < sel_pos &&
                  (next_pos < 0 || pos > next_pos)) {
                next = tb;
                next_pos = pos;
              }
            } else {
              if (pos <= max_pos && pos > sel_pos &&
                  (next_pos < 0 || pos < next_pos)) {
                next = tb;
                next_pos = pos;
              }
            }
          }
        }
      }
      if (!next) {
        if (offset < 0) {
          next = max;
          next_pos = max_pos;
        } else {
          next = min;
          next_pos = min_pos;
        }
      }
    }
    if (sel && next && next != sel) {
      stop_refilter_ = true;
      gtk_toggle_button_set_active(sel, FALSE);
      gtk_toggle_button_set_active(next, TRUE);
      stop_refilter_ = false;
      refilter();
      g_list_free(children);
      return true;
    }
    g_list_free(children);
  }
  return false;
}

void FilterBar::Selector::toggle() {
  if (is_visible()) {
    hide();
  } else {
    show();
  }
}

bool FilterBar::Selector::match(RefChannel ch) {
  if (!ch) return false;
  RefFavoriteMatch fm = ch->get_favorite_match();
  if (!is_visible()) return !fm->ng();
  bool filter_selected = false;
  for(FilterList::iterator fiter = filters_.begin();
      fiter != filters_.end(); ++fiter) {
    if (fiter->get_active() && fiter->get_enable()) {
      filter_selected = true;
      for(FavoriteMatch::iterator miter = fm->begin();
          miter != fm->end(); ++miter) {
        if (*miter == fiter->get_favorite()) {
          if (fm->ng()) {
            if (*(*miter)->ng()) return true;
          } else {
            return true;
          }
        }
      }
    }
  }
  return !filter_selected && !fm->ng();
}

void FilterBar::Selector::select(RefFavorite favorite) {
  stop_refilter_ = true;
  for(FilterList::iterator iter = filters_.begin();
      iter != filters_.end(); ++iter) {
    iter->set_active(iter->get_favorite() == favorite);
  }
  stop_refilter_ = false;
  refilter();
}
void FilterBar::Selector::unselect_all() {
  stop_refilter_ = true;
  for(FilterList::iterator iter = filters_.begin();
      iter != filters_.end(); ++iter) {
    iter->set_active(false);
  }
  stop_refilter_ = false;
  refilter();
}

void FilterBar::Selector::scroll_left() {
  if (show_start_ > 0) {
    --show_start_;
    update_size(get_allocation(), true);
  }
}
void FilterBar::Selector::scroll_right() {
  ++show_start_;
  update_size(get_allocation(), true);
}

bool FilterBar::Selector::bc_filterbar_show(ShellLine   &/*line*/,
                                            CommandOpts &/*opts*/) {
  show();
  return true;
}
bool FilterBar::Selector::bc_filterbar_hide(ShellLine   &/*line*/,
                                            CommandOpts &/*opts*/) {
  hide();
  return true;
}
bool FilterBar::Selector::bc_filterbar_toggle(ShellLine   &/*line*/,
                                              CommandOpts &/*opts*/) {
  toggle();
  return true;
}

FilterBar::Selector::Selector(Application &app, Gtk::Window &parent) :
  app_           (app),
  favorites_     (app_.favcollection()),
  scroller_      (*this),
  model_selector_(app),
  show_start_    (0),
  last_width_    (-1),
  filter_menu_   (app_, parent),
  stop_refilter_ (false),
  bc_filterbar_  (*app_.bc().add_ns("Filterbar"))
{
  favorites_->signal_item_added()
    .connect(sigc::mem_fun(*this, &Selector::on_add_item));
  favorites_->signal_item_removed()
    .connect(sigc::mem_fun(*this, &Selector::on_remove_item));
  favorites_->signal_item_reordered()
    .connect(sigc::mem_fun(*this, &Selector::on_reorder_item));
  favorites_->signal_cleared()
    .connect(sigc::mem_fun(*this, &Selector::on_clear));

  gtk_button_set_image(button_all_.gobj(),
                       gtk_image_new_from_stock(GTK_STOCK_CLEAR,
                                                GTK_ICON_SIZE_SMALL_TOOLBAR));
  button_all_.set_relief(Gtk::RELIEF_NONE);
  button_all_.signal_clicked()
    .connect(sigc::mem_fun(*this, &Selector::unselect_all));
  left_box_.pack_start(button_all_, false, false);
  left_box_.show_all();

  right_box_.pack_start(model_selector_, false, false);
  right_box_.show_all();

  pack_start(left_box_,  false, false);
  pack_end  (right_box_, false, false);
  pack_end  (scroller_,  false, false);

  set_no_show_all(true);
  add_events(Gdk::BUTTON_PRESS_MASK);

  property_visible().signal_changed()
    .connect(sigc::mem_fun(*this, &Selector::refilter));

#define ADD_CMD(NS, CMD, H)                                             \
  bc_##NS##_.add_cmd(#CMD, sigc::mem_fun(*this, &Selector::bc_##NS##_##CMD), H)
  ADD_CMD(filterbar, show,   _("Show filterbar"));
  ADD_CMD(filterbar, hide,   _("Hide filterbar"));
  ADD_CMD(filterbar, toggle, _("Toggle filterbar"));
#undef ADD_CMD
}

//========================
// FilterBar::SearchBar
void FilterBar::SearchBar::prepare_commandopts(CommandOpts &opts) {
  opts.ch = app_.get_selected_channel();
}
bool FilterBar::SearchBar::on_key_press_event(GdkEventKey *event) {
  if (event->type == GDK_KEY_PRESS) {
    switch(event->keyval) {
    case GDK_Up:
      app_.mainwindow()->channellist()->select_prev();
      return true;
    case GDK_Down:
      app_.mainwindow()->channellist()->select_next();
      return true;
    case GDK_Home:
      app_.mainwindow()->channellist()->select_first();
      return true;
    case GDK_End:
      app_.mainwindow()->channellist()->select_last();
      return true;
    }
  }
  return SearchBox::on_key_press_event(event);
}

void FilterBar::SearchBar::on_hide() {
  SearchBox::on_hide();
  app_.mainwindow()->channellist()->grab_focus();
}

FilterBar::SearchBar::SearchBar(Application &app, RefSearch search) :
  SearchBox(app, search, app.conf().input()->searchbar()),
  app_     (app)
{
}

//========================
// FilterBar
Rule::Target FilterBar::SEARCH_TARGETS =
  Rule::TARGET_NAME |
  Rule::TARGET_GENRE |
  Rule::TARGET_DESC |
  Rule::TARGET_COMMENT |
  Rule::TARGET_TRACK;

void FilterBar::main_on_run() {
  RefChannelList cl = app_.mainwindow()->channellist();
  cl->set_filter_func(sigc::mem_fun(*this, &FilterBar::filter_func));
  cl->set_search_widget(searchbar_, searchbar_.get_entry());
}

bool FilterBar::filter_func(RefChannel ch) {
  if (!filter_selector_.match(ch)) {
    return false;
  }
  return (search_->empty() ||
          search_->match(ch->get_name()) ||
          search_->match(ch->get_genre()) ||
          search_->match(ch->get_desc()) ||
          search_->match(ch->get_comment()) ||
          search_->match(ch->get_track_artist()) ||
          search_->match(ch->get_track_title()) ||
          search_->match(ch->get_track_album()));
}

void FilterBar::search_on_changed() {
  RefChannelList cl = app_.mainwindow()->channellist();
  cl->refilter();
  cl->select_first();
}

void FilterBar::searchbar_on_populate_popup(Gtk::Menu *menu) {
  Gtk::SeparatorMenuItem *sep = new Gtk::SeparatorMenuItem();
  sep->show();
  SimpleMenuItem *mi_create_filter =
    new SimpleMenuItem(_("Create filter"),
                       sigc::mem_fun(*this, &FilterBar::create_filter),
                       IconManager::FAVORITE);
  mi_create_filter->show();
  menu->append(*Gtk::manage(sep));
  menu->append(*Gtk::manage(mi_create_filter));
}

void FilterBar::create_filter() {
  RefSearch search = searchbar_.get_search();
  RefRegex  re     = search->get();
  if (re) {
    RefFavorite f = app_.favcollection()
      ->add_item(search->get_source(), true, true);
    f->add_item(re->get_source(), SEARCH_TARGETS);
    app_.ypcollection()->rematch_favorite();
    filter_selector_.select(f);
    searchbar_.hide();
  } else {
    log::err << "empty search" << log::endl;
  }
}

FilterBar::FilterBar(Application &app, Gtk::Window &parent) :
  conf::Element   ("filterbar"),
  app_            (app),
  search_         (new Search("search")),
  filter_selector_(app_, parent),
  searchbar_      (app_, search_)
{
  conf::Element::add(search_);

  search_->signal_changed().connect(sigc::mem_fun(*this, &FilterBar::search_on_changed));

  searchbar_.get_entry().signal_populate_popup()
    .connect(sigc::mem_fun(*this, &FilterBar::searchbar_on_populate_popup));
  searchbar_.set_no_show_all(true);

  pack_start(filter_selector_, false, true);
  pack_start(searchbar_,       false, true);

  Gtk::Main::signal_run()
    .connect(sigc::mem_fun(*this, &FilterBar::main_on_run));
}

//========================
// ChannelViewElement
const Channel::Property *ChannelViewElement::DEFAULT_PROPS[] = {
  &Channel::_prop_name, &Channel::_prop_id, &Channel::_prop_tip,
  &Channel::_prop_type, &Channel::_prop_name_url, &Channel::_prop_status,
  NULL
};

//========================
// ChannelView::PropMenuItem
void ChannelView::PropMenuItem::on_toggled() {
  ChPropList pl = *show_props_;
  ChPropList::iterator iter = std::find(pl.begin(), pl.end(), prop_);
  if (get_active()) {
    if (iter == pl.end()) {
      pl.push_back(prop_);
      show_props_ = pl;
    }
  } else {
    if (iter != pl.end()) {
      pl.erase(iter);
      show_props_ = pl;
    }
  }
}

ChannelView::PropMenuItem::PropMenuItem(RefAttributeChPropList show_props,
                                        const Channel::Property *prop) :
  Gtk::CheckMenuItem(prop->name),
  show_props_       (show_props),
  prop_             (prop)
{
}

//========================
// ChannelView
Gdk::Rectangle *ChannelView::on_show_property(Glib::RefPtr<Gtk::TextBuffer> buf,
                                              RefChannel                    ch,
                                              const Channel::Property      *prop,
                                              Gdk::Rectangle               &rect) {
  const ChPropList &pl = *conf_->show_props();
  if (std::find(pl.begin(), pl.end(), prop) == pl.end()) {
    return NULL;
  } else {
    return ChPropertyView::on_show_property(buf, ch, prop, rect);
  }
}
void ChannelView::on_populate_popup(Gtk::Menu *menu) {
  ChPropertyView::on_populate_popup(menu);
  const ChPropList &pl = *conf_->show_props();

  Gtk::Menu *menu_view = Gtk::manage(new Gtk::Menu());
  for(const Channel::Property **p = Channel::_properties; *p; ++p) {
    Gtk::CheckMenuItem *mi = Gtk::manage(new PropMenuItem(conf_->show_props(), *p));
    mi->set_active(std::find(pl.begin(), pl.end(), *p) != pl.end());
    menu_view->append(*mi);
  }

  Gtk::SeparatorMenuItem *sep = Gtk::manage(new Gtk::SeparatorMenuItem());
  Gtk::MenuItem *view = Gtk::manage(new Gtk::MenuItem(_("_View"), true));
  sep->show();
  view->set_submenu(*menu_view);
  view->show_all();
  menu->append(*sep);
  menu->append(*view);
}

ChannelView::ChannelView(Application &app, RefChannelViewElement conf) :
  panel::Page<ChPropertyView>(app, conf),
  conf_                      (conf)
{
  app.signal_channel_selected().connect(sigc::mem_fun(*this, &ChannelView::set));
  conn_conf_show_props_changed_ = conf_->show_props()->signal_changed()
    .connect(sigc::mem_fun(*this, &ChannelView::refresh));
}
ChannelView::~ChannelView() {
  conn_conf_show_props_changed_.disconnect();
}

//========================
// LogView
const int LogView::MAX_LINES = 40;

void LogView::view_on_populate_popup(Gtk::Menu *menu) {
  GtkWidget *sep = gtk_separator_menu_item_new();
  gtk_widget_show(sep);
  gtk_menu_append(menu->gobj(), sep);
  menu->append(*Gtk::manage(new CheckMenuItem(_("Show _debug log"), conf_->show_debug())));
}

void LogView::log_on_message(log::Logger::id_type id, const Glib::ustring &message) {
  if (log::dbg == id && !*conf_->show_debug()) return;

  ++erase_count_;

  Glib::RefPtr<Gtk::TextTag> tag;
  if (log::log == id) {
    tag = tag_log_;
  } else if (log::err == id) {
    tag = tag_err_;
  } else if (log::dbg == id) {
    tag = tag_dbg_;
  }
  Glib::RefPtr<Gtk::TextBuffer> buf = view_.get_buffer();
  if (tag) {
    buf->insert_with_tag(buf->end(), message, tag);
    buf->insert(buf->end(), "\n");
  } else {
    buf->insert_with_tag(buf->end(), "*** bug: failed to detect log type ***\n", tag_err_);
    buf->insert(buf->end(), message);
    buf->insert(buf->end(), "\n");
  }

  if (erase_count_ == 0) {
    erase_mark_ = buf->create_mark(buf->end());
  } else if (erase_count_ == MAX_LINES) {
    buf->erase(buf->begin(), erase_mark_->get_iter());
    buf->move_mark(erase_mark_, buf->end());
    erase_count_ = 0;
  }
}

void LogView::vadjustment_on_changed() {
  vadjustment_->set_value(vadjustment_->get_upper() - vadjustment_->get_page_size());
}
void
LogView::view_on_set_scroll_adjustments(Gtk::Adjustment */*hadjustment*/, Gtk::Adjustment *vadjustment) {
  conn_vadjustment_changed_.disconnect();
  vadjustment_ = vadjustment;
  if (vadjustment_) {
    conn_vadjustment_changed_ = vadjustment_->signal_changed()
      .connect(sigc::mem_fun(*this, &LogView::vadjustment_on_changed));
  }
}

void LogView::command_entry_on_activate() {
  CommandOpts opts;
  opts.ch = app_.get_selected_channel();
  app_.commandrunner().run(command_entry_.get_text(), &opts);
  command_entry_.set_text(Glib::ustring());
}

bool LogView::bc_clear(ShellLine &/*line*/, CommandOpts &/*opts*/) {
  view_.get_buffer()->set_text(Glib::ustring());
  return true;
}

LogView::LogView(Application &app, RefLogViewElement conf) :
  panel::Page<Gtk::VBox>(conf),
  app_          (app),
  conf_         (conf),
  connected_    (false),
  view_         (app, true),
  command_entry_(app),
  erase_count_  (-MAX_LINES),
  bc_logview_   (*app_.bc().add_ns("LogView"))
{
  view_.set_editable(false);
  view_.signal_set_scroll_adjustments()
    .connect(sigc::mem_fun(*this, &LogView::view_on_set_scroll_adjustments));
  view_.signal_populate_popup()
    .connect(sigc::mem_fun(*this, &LogView::view_on_populate_popup));

  command_entry_.signal_activate()
    .connect(sigc::mem_fun(*this, &LogView::command_entry_on_activate));

#define TAG_ATTACH_COLOR(N, T) {                \
    app.conf().general()->color_##N()           \
      ->attach(T->property_foreground_gdk());   \
    app.conf().general()->color_##N()           \
      ->attach(T->property_foreground_set());   \
  }
  Glib::RefPtr<Gtk::TextBuffer> buf = view_.get_buffer();
  tag_log_ = buf->create_tag();
  tag_err_ = buf->create_tag();
  tag_dbg_ = buf->create_tag();
  TAG_ATTACH_COLOR(positive,    tag_log_);
  TAG_ATTACH_COLOR(negative,    tag_err_);
  TAG_ATTACH_COLOR(unimportant, tag_dbg_);
#undef TAG_ATTACH_COLOR

  log::MessageQueueEntry *messages = log::get_messages();
  for(log::MessageQueueEntry *m = messages; m; m = m->next) {
    log_on_message(m->id, *m->msg);
  }
  log::signal_message().connect(sigc::mem_fun(*this, &LogView::log_on_message));

  sw_view_.add(view_);
  sw_view_.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);

  pack_start(sw_view_);
  pack_start(command_entry_, false, true);
  set_border_width(2);
  set_spacing(2);

#define ADD_CMD(NS, CMD, H)                                             \
  bc_##NS##_.add_cmd(#CMD, sigc::mem_fun(*this, &LogView::bc_##CMD), H)
  ADD_CMD(logview, clear, _("Clear logs"));
#undef ADD_CMD
}
LogView::~LogView() {
  app_.bc().remove(bc_logview_);
  conn_vadjustment_changed_.disconnect();
}

//========================
// PanelElement
PanelElement::PanelElement(const Glib::ustring &name) :
  panel::PanelElement(name),
  processes_         (new ProcessViewElement("processview", true)),
  peercast_          (new PCRelayViewElement("pcrelayview", true)),
  channel_           (new ChannelViewElement("channelview", true)),
  log_               (new LogViewElement    ("logview",     true))
{
  add(processes_);
  add(peercast_);
  add(channel_);
  add(log_);
}

//========================
// Panel
panel::PageBase *Panel::page_create_processes() {
  return page_processes_ = new ProcessView(app_, conf_->processes());
}
panel::PageBase *Panel::page_create_peercast() {
  return page_peercast_ = new PCRelayView(app_, conf_->peercast());
}
panel::PageBase *Panel::page_create_channel() {
  return page_channel_ = new ChannelView(app_, conf_->channel());
}
panel::PageBase *Panel::page_create_log() {
  return page_log_ = new LogView(app_, conf_->log());
}

Panel::Panel(Application         &app,
             RefPanelElement      conf,
             RefAttributePanelPos position,
             Gtk::Paned          &paned) :
  panel::Panel   (app, conf, position, paned),
  app_           (app),
  conf_          (conf),
  page_processes_(NULL),
  page_peercast_ (NULL),
  page_channel_  (NULL),
  page_log_      (NULL)
{
#define PAGE(NAME, ICON, LABEL, SW)                                     \
  install_page(conf->NAME(), sigc::mem_fun(*this, &Panel::page_create_##NAME), ICON, LABEL, SW)
  PAGE(processes, IconManager::TERMINAL, _("Processes"), false);
  PAGE(peercast,  IconManager::PEERCAST, _("PeerCast"),  false);
  PAGE(channel,   GTK_STOCK_PROPERTIES,  _("Channel"),   true);
  PAGE(log,       IconManager::LOG,      _("Log"),       false);
#undef PAGE
}

//========================
// StatusBar
void StatusBar::popup_on_deactivate() {
  conn_popup_deactivate_.disconnect();
  in_popup_ = false;
  if (hold_update_) update_text();
}
void StatusBar::on_populate_popup(Gtk::Menu *menu) {
  EntryLabel::on_populate_popup(menu);
  conn_popup_deactivate_.disconnect();
  conn_popup_deactivate_ = menu->signal_deactivate()
    .connect(sigc::mem_fun(*this, &StatusBar::popup_on_deactivate));
  in_popup_ = true;
}

void StatusBar::on_channel_selected(RefChannel /*ch*/) {
  update_text();
}
void StatusBar::update_text() {
  if (in_popup_ || has_focus()) {
    hold_update_ = true;
  } else {
    Glib::ustring fmt;
    format_->format(fmt, app_.get_selected_channel());
    set_text(fmt);
    hold_update_ = false;
  }
}

bool StatusBar::on_focus_out_event(GdkEventFocus *event) {
  if (hold_update_) update_text();
  return EntryLabel::on_focus_out_event(event);
}

StatusBar::StatusBar(Application &app) :
  conf::Element("statusbar"),
  EntryLabel   (app),
  app_         (app),
  format_      ("format", "[%{YP.chcount_valid}/%{YP.chcount}]%{Channel.ypname}:%{Channel.longdesc}", app),
  hold_update_ (false),
  in_popup_    (false)
{
  app_.signal_channel_selected().connect(sigc::mem_fun(*this, &StatusBar::on_channel_selected));
  //app_.ypcollection()->signal_loaded()
  //  .connect(sigc::mem_fun(*this, &StatusBar::update_text));
}

}/*gpyp*/
